Skip to content

fix(parser): preserve TrackedSet on plural 'those tokens' delayed exile#6199

Merged
matthewevans merged 6 commits into
phase-rs:mainfrom
andriypolanski:fix/5972-saheeli-those-tokens-tracked-set
Jul 19, 2026
Merged

fix(parser): preserve TrackedSet on plural 'those tokens' delayed exile#6199
matthewevans merged 6 commits into
phase-rs:mainfrom
andriypolanski:fix/5972-saheeli-those-tokens-tracked-set

Conversation

@andriypolanski

Copy link
Copy Markdown
Contributor

Closes #5972

Summary

Discord report: Saheeli, the Gifted's [-7] creates a token copy of each artifact you control, grants them haste, then should exile all of those tokens at the next end step — but only the last-created token was exiled.

Root cause: after rewrite_parent_targets_to_tracked_set correctly bound plural "those tokens" to TrackedSet { id: 0 } with uses_tracked_set: true, the post-token resolve_populated_token_anaphors pass rewrote TrackedSetLastCreated on the delayed ChangeZone exile. LastCreated only snapshots the final token in a multi-token batch.

Fix: preserve TrackedSet in rewrite_parent_target_to_last_created's ChangeZone arm. Singular "it"/"that token" cleanup (Flameshadow Conjuring, Inalla) still rewrites ParentTarget/TriggeringSourceLastCreated.

Changes

  • crates/engine/src/parser/oracle_effect/lower.rs — stop stomping TrackedSet to LastCreated on delayed plural-token exile cleanup.
  • crates/engine/src/parser/oracle_effect/tests.rs — Saheeli -7 regression: delayed exile stays TrackedSet + uses_tracked_set: true.
  • crates/engine/src/parser/oracle_tests.rs — Twinflame regression: inner delayed exile target is TrackedSet, not LastCreated.

Test Plan

  • cargo fmt --all -- --check
  • cargo test -p engine --lib saheeli_minus_seven_those_tokens_delayed_exile_keeps_tracked_set
  • cargo test -p engine --lib twinflame_full_parse
  • cargo test -p engine --lib etb_token_copier_exile_anaphor_binds_created_token
  • cargo test -p engine --lib delayed_trigger_change_zone_after_token_creator_rewrites_to_last_created
  • Regenerate card-data (./scripts/gen-card-data.sh or Tilt card-data) so Saheeli's [-7] execute body picks up the parser fix in runtime

@superagent-security superagent-security Bot added the contributor:flagged Contributor flagged for review by trust analysis. label Jul 19, 2026
@superagent-security

Copy link
Copy Markdown

🚨 Contributor flagged. Click here for more info: Superagent Dashboard

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request resolves issue #5972 by ensuring that plural 'those tokens' references, which are rewritten to TrackedSet, are not incorrectly overwritten by LastCreated in rewrite_parent_target_to_last_created. The match criteria in lower.rs have been updated to exclude TargetFilter::TrackedSet, and the twinflame_full_parse test has been expanded to assert that the delayed trigger's zone change correctly targets the TrackedSet. No review comments were provided, so there is no additional feedback to address.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown

Parse changes introduced by this PR · 1 card(s), 1 signature(s) (baseline: main 777dbabf8a38)

🟡 Modified fields (1 signature)

  • 1 card · 🔄 ability/ChangeZone · changed field target: last createdtracked set #0
    • Affected (first 3): Mysterio, Master of Illusion

1 card(s) had Oracle-text changes (errata/reprint) — excluded as non-parser.

@matthewevans matthewevans self-assigned this Jul 19, 2026

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes — the parser change identifies the plural anaphor but sends battlefield tokens to an exile-only delayed-trigger resolver.

🔴 Blockers

  • crates/engine/src/parser/oracle_effect/lower.rs:3032-3045 preserves TrackedSet for the cleanup, but crates/engine/src/game/effects/delayed_trigger.rs:687-737 then turns every delayed ChangeZone { TrackedSet } into ChangeZoneAll { origin: Exile }. The current parse artifact already shows Mysterio's cleanup origin changing battlefield → ∅. Saheeli, Mysterio, and Twinflame create the referenced tokens on the battlefield, so this resolver cannot exile them. Move the completion semantics to a zone/provenance-aware tracked-set binding at bind_tracked_set_to_effect, and add a production delayed-trigger regression that creates multiple tokens, resolves the cleanup, and proves a token that left the battlefield first is not found.

  • The current head fails parser::oracle::tests::altair_ibn_la_ahad_for_each_exile_memory_counter_copy_parses at crates/engine/src/parser/oracle_tests.rs:800: Altaïr's printed “Exile those tokens at end of combat” now lowers to TrackedSet, while the stale test requires LastCreated. Update this sibling through the same runtime design; do not restore the single-token binding.

  • The current <!-- coverage-parse-diff --> reports only Mysterio, while the PR claims Saheeli. parse_effect_chain shape assertions do not prove the actual card pipeline or resolution. Reconcile the card-level diff and add the runtime proof for the claimed card class.

✅ Clean

  • Scryfall confirms the relevant plural cleanup wording for Saheeli, Mysterio, and Altaïr. The parse-diff is current, and the existing Gemini review raised no separate technical finding.

Recommendation: request changes; move the completion semantics into the delayed-trigger tracked-set binding seam, then cover the multiple-token and left-battlefield cases through the real resolution pipeline.

@matthewevans matthewevans added the bug Bug fix label Jul 19, 2026
@matthewevans matthewevans removed their assignment Jul 19, 2026
@matthewevans matthewevans self-assigned this Jul 19, 2026
@matthewevans

Copy link
Copy Markdown
Member

Maintainer fixup pushed to de8a5fa9867f4dc349b52b95fc1fad9331d7f9ec.

  • Preserved the explicit battlefield origin for plural token cleanup while retaining the Exile default for generic tracked-card returns.
  • Replaced the full card-data test load with the committed integration fixture, addressing the CI gate.
  • Restored unrelated generated-file drift so the PR diff is limited to the tracked-set change and its tests.

The refreshed CI suite is now the verification authority.

@matthewevans matthewevans removed their assignment Jul 19, 2026
@andriypolanski

Copy link
Copy Markdown
Contributor Author

Hi, @matthewevans
This pr is working well without any error block.

@matthewevans

Copy link
Copy Markdown
Member

Acknowledged. The maintainer fixup is still the current head, but its current CI run is red, so this remains blocked pending the failing-check diagnosis and a green rerun.

@andriypolanski
andriypolanski force-pushed the fix/5972-saheeli-those-tokens-tracked-set branch from de8a5fa to 903d268 Compare July 19, 2026 17:07
@matthewevans matthewevans self-assigned this Jul 19, 2026
@matthewevans

Copy link
Copy Markdown
Member

Maintainer fixup pushed to 25c0b2fe3c75b985008a12e607883e6bc69ca081.

The failing assertion required Saheeli in the deliberately curated integration fixture, but that fixture contains Twinflame only. I removed the invalid duplicate card-data assertion; the parser-shape test and the real Twinflame delayed-cleanup regression remain. CI has restarted for the new head.

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved after the maintainer fixups and branch update. The delayed-trigger binding now preserves the battlefield-origin tracked set, the remaining runtime regression exercises two Twinflame tokens through end-step cleanup, and the invalid fixture assertion was removed. Required CI is still running; merge queue will wait for it.

@matthewevans
matthewevans enabled auto-merge July 19, 2026 17:29
@matthewevans matthewevans removed their assignment Jul 19, 2026
@matthewevans
matthewevans added this pull request to the merge queue Jul 19, 2026
Merged via the queue into phase-rs:main with commit 5931f7a Jul 19, 2026
12 of 13 checks passed
lgray added a commit to lgray/phase that referenced this pull request Jul 21, 2026
Review feedback (phase-rs#6274): the non-vacuity floors shipped at 2845/1480 sat
exactly at the PRE-refresh catalog, so reverting this PR's regen still
passed every assertion — the guard could not detect losing the refresh it
was added alongside. The pre-PR test asserted the parent counts as
equalities (assert_eq!(total_tokens, 2845)) and was green on main, so the
parent catalog measures exactly those floors.

Ratchet all floors to the committed vintage (2858/1490) and add the third
axis already carried on the summary, source_card_refs >= 9821. `>=` still
keeps weekly upstream ADDITIONS green — the false-red phase-rs#6237 introduced —
while any shrink now fails.

The ref floor is the load-bearing one and is deliberately tight: across the
ten recorded revisions of known-tokens.toml, tokens and rules_text are
monotone, and source_card_refs shrank exactly once — phase-rs#6199, a parser PR
that silently dropped 1173 token<->card links (9810 -> 8637) while the
token count GREW past a count-based floor. This regen is what repaired it.

Each floor is probed in isolation, since sequential assert!s let an earlier
conjunct dominate a later one and render it vacuous:
  - catalog reverted to f0ac543^   -> FAIL "token catalog shrank: 2845 presets < 2858"
  - 1 rules_text line removed        -> FAIL rules_text_tokens >= 1490
  - 5 source_card_refs blocks removed -> FAIL source_card_refs >= 9821
Catalog restored byte-identically after each probe.

Floors carry the measured count in the panic so a CI red names the axis and
the delta instead of requiring a local repro.

Assisted-by: ClaudeCode:claude-opus-4.8
lgray added a commit to lgray/phase that referenced this pull request Jul 21, 2026
Review feedback (phase-rs#6274): the non-vacuity floors shipped at 2845/1480 sat
exactly at the PRE-refresh catalog, so reverting this PR's regen still
passed every assertion — the guard could not detect losing the refresh it
was added alongside. The pre-PR test asserted the parent counts as
equalities (assert_eq!(total_tokens, 2845)) and was green on main, so the
parent catalog measures exactly those floors.

Ratchet all floors to the committed vintage (2858/1490) and add the third
axis already carried on the summary, source_card_refs >= 9821. `>=` still
keeps weekly upstream ADDITIONS green — the false-red phase-rs#6237 introduced —
while any shrink now fails.

The ref floor is the load-bearing one and is deliberately tight: across the
ten recorded revisions of known-tokens.toml, tokens and rules_text are
monotone, and source_card_refs shrank exactly once — phase-rs#6199, a parser PR
that silently dropped 1173 token<->card links (9810 -> 8637) while the
token count GREW past a count-based floor. This regen is what repaired it.

Each floor is probed in isolation, since sequential assert!s let an earlier
conjunct dominate a later one and render it vacuous:
  - catalog reverted to f0ac543^   -> FAIL "token catalog shrank: 2845 presets < 2858"
  - 1 rules_text line removed        -> FAIL rules_text_tokens >= 1490
  - 5 source_card_refs blocks removed -> FAIL source_card_refs >= 9821
Catalog restored byte-identically after each probe.

Floors carry the measured count in the panic so a CI red names the axis and
the delta instead of requiring a local repro.

Assisted-by: ClaudeCode:claude-opus-4.8
matthewevans pushed a commit that referenced this pull request Jul 21, 2026
…hot vintage-proof (#6274)

* fix(engine): commit W30 MTGJSON token catalog and make coverage snapshot vintage-proof

The weekly MTGJSON refresh introduced by #6237 (vintage gate 2026-07-20)
unfroze a stale token catalog cache. Deterministic regen (byte-identical
across two checkouts) against the 2026-07-20 MTGJSON data adds 13 tokens
(12 Secret Lair Drop printings: Food x8, Treasure, Ooze, Elf Warrior x2;
1 HOB Goblin Army) and 1184 [[token.source_card_refs]] (8637 -> 9821),
and picks up MTGJSON's face-name change 'Undercity // The Initiative' ->
'Undercity'. No tokens removed; coverage stays complete (2858/2858
supported, 1490/1490 rules-text tokens parsed).

analyze_token_coverage_treats_source_defined_pt_as_represented pinned
vintage-dependent absolutes (2845/1480), which go red every week upstream
adds tokens even at 100% coverage. Replace them with invariants
(supported == total, parsed == rules_text) plus non-vacuity floors at
the last-known-good baseline so an empty or truncated catalog cannot
pass vacuously.

Assisted-by: ClaudeCode:claude-fable-5

* test(engine): ratchet token-catalog floors to the committed vintage

Review feedback (#6274): the non-vacuity floors shipped at 2845/1480 sat
exactly at the PRE-refresh catalog, so reverting this PR's regen still
passed every assertion — the guard could not detect losing the refresh it
was added alongside. The pre-PR test asserted the parent counts as
equalities (assert_eq!(total_tokens, 2845)) and was green on main, so the
parent catalog measures exactly those floors.

Ratchet all floors to the committed vintage (2858/1490) and add the third
axis already carried on the summary, source_card_refs >= 9821. `>=` still
keeps weekly upstream ADDITIONS green — the false-red #6237 introduced —
while any shrink now fails.

The ref floor is the load-bearing one and is deliberately tight: across the
ten recorded revisions of known-tokens.toml, tokens and rules_text are
monotone, and source_card_refs shrank exactly once — #6199, a parser PR
that silently dropped 1173 token<->card links (9810 -> 8637) while the
token count GREW past a count-based floor. This regen is what repaired it.

Each floor is probed in isolation, since sequential assert!s let an earlier
conjunct dominate a later one and render it vacuous:
  - catalog reverted to f0ac543^   -> FAIL "token catalog shrank: 2845 presets < 2858"
  - 1 rules_text line removed        -> FAIL rules_text_tokens >= 1490
  - 5 source_card_refs blocks removed -> FAIL source_card_refs >= 9821
Catalog restored byte-identically after each probe.

Floors carry the measured count in the panic so a CI red names the axis and
the delta instead of requiring a local repro.

Assisted-by: ClaudeCode:claude-opus-4.8

* docs(engine): document the provenance seam for the token-catalog floors

The floors observe totals; provenance (committed catalog == tokens-gen output
for the declared MTGJSON vintage) is structurally untestable at test time (the
~560 MB gitignored generator input is absent; the test sees build.rs's embed of
the tracked file) and is verified at the generation seam instead
(gen-card-data.sh temp+cmp+vintage gate). Comment documents the seam and the
by-hand reproduction recipe, per review 4746023659.

Assisted-by: ClaudeCode:claude-opus-4.8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Bug fix contributor:flagged Contributor flagged for review by trust analysis.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Saheeli, the Gifted bug — The delayed trigger created by Saheeli's [-7] ability doesn't exile any tokens

3 participants